import {getCurrentInstance, onBeforeUpdate, reactive} from 'vue'
import {VNodeChild} from "../shims";

type SlotFunction = ((vnode?: VNodeChild) => VNodeChild) & { isExist: () => boolean }

type SlotsData<T extends string> = {
  slots: { default: SlotFunction } & { [k in T]: SlotFunction }
}

export function useSlots<T extends string>(names?: T[]): SlotsData<T> {

  const ctx = getCurrentInstance()!
  const slotNames = [...(names || []), 'default']

  /**
   * 因为ctx.slots,ctx.ctx.$slots都不是响应式属性，无法触发computed以及watch中的变化，这里做一个手动更新处理
   * 每次beforeUpdate打补丁之前，先更新ctxSlots响应式变量，render函数只会触发一次；
   * @author  韦胜健
   * @date    2020/10/19 11:36
   */
  const ctxSlots = reactive(slotNames.reduce((prev: any, slotName) => (prev[slotName] = ctx.slots[slotName] || null, prev), {}) as any)
  onBeforeUpdate(() => slotNames.forEach(slotName => ctxSlots[slotName] = ctx.slots[slotName] || null))

  /*slots.***是一个函数，参数是插槽后备内容。当插槽不存在时，渲染后备内容，否则渲染插槽内容*/
  let slots = slotNames.reduce((prev: any, slotName: string) => {
    prev[slotName] = Object.assign((vnode: VNodeChild) => {
      const slot = ctxSlots[slotName]
      return !!slot ? slot() : vnode
    }, {
      isExist() {
        return !!ctxSlots[slotName]
      }
    })
    return prev
  }, {} as SlotsData<T>["slots"])

  return {
    slots,
  }
}

/*我们封装一个 useSlots 函数，目的有两个：

规范组件中使用插槽的方式，所有可能用到的插槽，都需要通过 useSlots定义，以便组件的维护人员在浏览器组件代码的时候，能够立刻分辨出组件支持的插槽有哪些。而不是在render函数中，一个一个地去搜索 slots 关键字。

因为插槽没有作用域参数，所以通过执行插槽函数获取插槽内容，这种方式有点多余。这里我们改造用法为：函数接收一个类型为 VNodeChild的 vnode 参数，当存在插槽时，渲染插槽内容，否则渲染vnode，也就是说，插槽函数的第一个参数为预备内容。
*/
